#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#define V3S_KO_CNT 1
#define V3S_KO_NAME "v3sKoLed"
// pin54 PC2 SPI-CS 1-Led On; 0-Led Off

static char g_dataBuf[100] = "empty buffer";

struct v3sKoDev
{
    dev_t devID;
    struct cdev cdev;
    struct class* class;
    struct device* device;
    int major;
    int minor;

    struct device_node *nd;
    int ledGpio;
};

struct v3sKoDev g_dev;

void V3sKoLedToggle(uint32_t ioType)
{
    printk("V3sKoLed -> ioType[%u]\r\n", ioType);
}

static int V3sKoLed_Open(struct inode* inode, struct file* pFile)
{
    printk("V3sKoLed_Open\r\n");
    pFile->private_data = &g_dev;

    return 0;
}

static ssize_t V3sKoLed_Read(struct file *pFile, char __user *buf, size_t cnt, loff_t *offt)
{
    (void)pFile;
    printk(KERN_DEBUG"V3sKoLed_Read\r\n");
    int ret = copy_to_user(buf, g_dataBuf, cnt);
    printk(KERN_DEBUG"kernel send data ret[%u], %s\r\n", ret, ret == 0 ? "succeed" : "failed");

    return 0;
}

static ssize_t V3sKoLed_Write(struct file *pFile, const char __user *buf, size_t cnt, loff_t *offt)
{
    if (pFile == NULL) {
        printk(KERN_DEBUG"kernel pFile is null\r\n");
        return -1;
    }
    struct v3sKoDev* dev = pFile->private_data;
    if (dev == NULL || (dev->ledGpio < 0)) {
        printk(KERN_DEBUG"kernel dev is null\r\n");
        return -1;
    }
    printk(KERN_DEBUG"V3sKoLed_Write\r\n");
    memset(g_dataBuf, 0, sizeof(g_dataBuf));
    int ret = copy_from_user(g_dataBuf, buf, cnt);
    printk(KERN_DEBUG"kernel recv[%d]: %s\r\n", cnt, g_dataBuf);
    int ledVal = g_dataBuf[0] == '1' ? 1 : 0;
    printk(KERN_DEBUG"writing %u to led...\r\n", ledVal);
    gpio_set_value(dev->ledGpio, ledVal);
    return 0;
}

static int V3sKoLed_release(struct inode* inode, struct file *pFile)
{
    printk("V3sKoLed_release\r\n");
    return 0;
}

static struct file_operations v3sKoLed_fops = {
    .owner = THIS_MODULE,
    .open = V3sKoLed_Open,
    .read = V3sKoLed_Read,
    .write = V3sKoLed_Write,
    .release = V3sKoLed_release,
};

static int __init V3sKoLed_Init(void)
{
    const char* nodePath = "/leds/sta_led";
    g_dev.nd = of_find_node_by_path(nodePath);
    if (g_dev.nd == NULL) {
        printk("%s not exist\r\n", nodePath);
        return -EINVAL;
    }
    g_dev.ledGpio = of_get_named_gpio(g_dev.nd, "gpios", 0);
    // /leds/green_led 192
    // /leds/blue_led 193
    // /leds/red_led 194
    if (g_dev.ledGpio < 0) {
        printk("get 'gpios' from %s failed\r\n", nodePath);
        return -EINVAL;
    }
    printk("get 'gpios' from %s: %d\r\n", nodePath, g_dev.ledGpio);

    int ret = gpio_direction_output(g_dev.ledGpio, 0);
    if (ret < 0) {
        printk("set gpio output failed with %d\r\n", ret);
    }
    if (g_dev.major) {
        g_dev.devID = MKDEV(g_dev.major, 0);
        // regisiter_chrdev_region(g_dev.devID, V3S_KO_CNT, V3S_KO_NAME);
    } else {
        alloc_chrdev_region(&g_dev.devID, 0, V3S_KO_CNT, V3S_KO_NAME);
        g_dev.major = MAJOR(g_dev.devID);
        g_dev.minor = MINOR(g_dev.devID);
    }
    printk("V3sKoLed_Init register major[%d], minor[%d]\r\n", g_dev.major, g_dev.minor);

    g_dev.cdev.owner = THIS_MODULE;
    cdev_init(&g_dev.cdev, &v3sKoLed_fops);
    cdev_add(&g_dev.cdev, g_dev.devID, V3S_KO_CNT);
    g_dev.class = class_create(THIS_MODULE, V3S_KO_NAME);
    if (IS_ERR(g_dev.class)) {
        printk("V3sKoLed_Init class_create falied\r\n");
        return PTR_ERR(g_dev.class); // TODO: 有内存泄漏的风险 注意使用 cdev_del unregister_chrdev_region 释放
    }

    g_dev.device = device_create(g_dev.class, NULL, g_dev.devID, NULL, V3S_KO_NAME);
    if (IS_ERR(g_dev.device)) {
        printk("V3sKoLed_Init device_create falied\r\n");
        return PTR_ERR(g_dev.device);
    }

    return 0;
}

static void __exit V3sKoLed_Exit(void)
{
    cdev_del(&g_dev);
    unregister_chrdev_region(g_dev.devID, V3S_KO_CNT);
    device_destroy(g_dev.class, g_dev.devID);
    class_destroy(g_dev.class);
    printk("V3sKoLed_Exit");
}

module_init(V3sKoLed_Init);
module_exit(V3sKoLed_Exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Raiiwhen2024");